package com.tmt.im.manager.service;

import com.tmt.im.common.pojo.ChatSessionPoJo;
import com.tmt.im.common.pojo.IMUserPoJo;
import com.tmt.im.jpa.entity.SystemParam;
import com.tmt.im.jpa.entity.UploadedFile;
import com.tmt.im.jpa.repository.SystemParamRepo;
import com.tmt.im.jpa.repository.UploadedFileRepo;
import com.tmt.im.manager.config.beans.CurrentServiceProperties;
import com.tmt.im.manager.controller.api.FileController;
import com.tmt.im.manager.controller.vo.UploadFileRequest;
import com.tmt.im.manager.helper.PrincipalHelper;
import com.tmt.im.manager.interfaces.IYunXinInterface;
import com.tmt.springboot.common.helper.IdSnowFlake;
import com.tmt.springboot.common.restful.ExecuteResult;
import com.tmt.springboot.common.restful.IExecuteResult;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.http.Method;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;

/**
 * 用户头像服务
 *
 * @Description TODO
 * @Author wuyi
 * @Date 2024/8/25 16:13
 * @Version 1.0
 **/
@Slf4j
@Service
public class FileService {

    private final ApplicationContext applicationContext;

    private final MinioClient minioClient;

    private final String bucket;

    private final String apiUrlPrefix;

    private String avatarUrlPrefix;

    private final CurrentServiceProperties serviceProperties;

    private final UploadedFileRepo uploadedFileRepo;

    private final SystemParamRepo systemParamRepo;

    private final IgniteCache<Long, IMUserPoJo> userCache;

    private final IgniteCache<Long, ChatSessionPoJo> chatSessionCache;

    private final IYunXinInterface iYunXinInterface;

    public FileService(ApplicationContext applicationContext,
                       Ignite ignite,
                       @Value("${minio.bucket}")
                       String bucket,
                       @Value("${api-url-prefix}")
                       String apiUrlPrefix,
                       MinioClient minioClient,
                       CurrentServiceProperties serviceProperties,
                       UploadedFileRepo uploadedFileRepo,
                       SystemParamRepo systemParamRepo,
                       IYunXinInterface iYunXinInterface) {
        this.applicationContext = applicationContext;
        this.minioClient = minioClient;
        this.bucket = bucket;
        this.apiUrlPrefix = apiUrlPrefix;
        this.serviceProperties = serviceProperties;
        this.uploadedFileRepo = uploadedFileRepo;
        this.systemParamRepo = systemParamRepo;

        this.chatSessionCache = ignite.cache(ChatSessionPoJo.CACHE_NAME);
        this.userCache = ignite.cache(IMUserPoJo.CACHE_NAME);
        this.iYunXinInterface = iYunXinInterface;
    }


    /**
     * 将上传的文件保存到minio中。
     *
     * @param request
     * @param principal
     * @return
     */
    public IExecuteResult<String> uploadFile(UploadFileRequest request, Principal principal) {
        if (request.getFile() == null) {
            return ExecuteResult.failure("上传文件不能为null", null);
        }

        if (request.getType() == 1 && chatSessionCache.get(request.getTid()) == null) {
            return ExecuteResult.failure("目标群组不存在", null);
        }

        Long currentUserId = PrincipalHelper.getCurrentUserId(principal);
        int type = request.getType();
        MultipartFile file = request.getFile();

        try {
            String fileName = file.getOriginalFilename();
            assert fileName != null;

            //获取文件扩展名称。
            int index = fileName.lastIndexOf(".");
            String ext = index > 0 && index + 1 < fileName.length() ? fileName.substring(index) : "";

            SystemParam param = systemParamRepo.findOneByParamCode("valid_image_types");
            List<String> validImageTypes = Arrays.stream(param.getParamValue().split(",")).map(String::trim).toList();

            if (type == 0 || type == 1) {
                //获取允许上传的头像文件类型。
                if (validImageTypes.stream().noneMatch(x -> x.equals(ext.substring(1)))) {
                    throw new Exception(MessageFormat.format("头像文件类型仅支持：{0}", param.getParamValue()));
                }
            }

            String path = type == 0 ? "user" : (type == 1 ? "group" : "file");
            Long fileId = type == 0 ? currentUserId : (type == 1 ? request.getTid() : IdSnowFlake.getId());
            Assert.isTrue(fileId != null, "fileId不能为null");
            //文件名称。
            fileName = MessageFormat.format("{0}/{1}{2}", path, "" + fileId, ext);

            //确定ContentType参数
            String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
            if (type == 0 || type == 1 || validImageTypes.contains(ext.substring(1))) {
                if (ext.toLowerCase().endsWith("png")) {
                    contentType = MediaType.IMAGE_PNG_VALUE;
                } else if (ext.toLowerCase().endsWith("jpg")) {
                    contentType = MediaType.IMAGE_JPEG_VALUE;
                } else {
                    contentType = MediaType.IMAGE_GIF_VALUE;
                }
            }

            //将文件保存到minio中。
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(this.bucket)      // 指定 Bucket
                    .contentType(contentType)// 指定 Content Type
                    .object(fileName)        // 指定文件的路径
                    .stream(file.getInputStream(), file.getSize(), -1) // 文件的 Inputstream 流
                    .build());

            if (!StringUtils.hasText(avatarUrlPrefix)) {
                FileController controller = applicationContext.getBean(FileController.class);
                RequestMapping ann = null;
                if (controller.getClass().getSimpleName().endsWith(".FileController")) {
                    ann = controller.getClass().getAnnotation(RequestMapping.class);
                } else {
                    ann = ((Class<?>) controller.getClass().getGenericSuperclass()).getAnnotation(RequestMapping.class);
                }
                assert ann != null;
                avatarUrlPrefix = ann.value()[0];
                avatarUrlPrefix = avatarUrlPrefix.replace("${api-url-prefix}", apiUrlPrefix);
            }

            path = type == 0 ? "avatar/user" : (type == 1 ? "avatar/group" : "download");
            String fileUri = MessageFormat.format("{0}/{1}/{2}{3}", avatarUrlPrefix, path, "" + fileId, ext);
            fileUri = fileUri.replaceAll("//", "/");

            boolean needSyncYunXin = false;
            UploadedFile uploadedFile = null;
            if (type == 2) {
                uploadedFile = createUploadFileData(fileId, type, contentType, file.getOriginalFilename(),
                        ext.substring(1), fileUri, file.getSize());
            } else {
                uploadedFile = uploadedFileRepo.findById(fileId).orElse(null);
                if (uploadedFile == null) {
                    needSyncYunXin = !ext.substring(1).equalsIgnoreCase("png");
                    uploadedFile = createUploadFileData(fileId, type, contentType, file.getOriginalFilename(),
                            ext.substring(1), fileUri, file.getSize());
                } else {
                    needSyncYunXin = !ext.substring(1).equalsIgnoreCase(uploadedFile.getSuffixType());
                    uploadedFile.setContentType(contentType);
                    uploadedFile.setFileName(file.getOriginalFilename());
                    uploadedFile.setSuffixType(ext.substring(1));
                    uploadedFile.setFileUri(fileUri);
                    uploadedFile.setFileSize(file.getSize());
                    uploadedFile.setCreateTime(new Timestamp(System.currentTimeMillis()));
                }
            }
            uploadedFileRepo.save(uploadedFile);

            String avatar = MessageFormat.format("http://{0}{1}", serviceProperties.getExternalClientService(), fileUri);
            if (needSyncYunXin) {
                //更新缓存和数据库。
                StringBuilder error = new StringBuilder();
                if (type == 0) {
                    IMUserPoJo userPoJo = userCache.get(currentUserId);
                    userPoJo.setInsert(false);
                    userPoJo.setAvatar(fileUri);
                    userCache.put(userPoJo.getKey(), userPoJo);
                    //如果是用户头像或群组头像，还需要同步到云信平台。
                    if (!iYunXinInterface.updateUserAvatar(userPoJo.getId(), avatar, error)) {
                        System.out.println("---同步用户头像信息到云信平台异常：" + error);
                    }
                } else if (type == 1) {
                    ChatSessionPoJo sessionPoJo = chatSessionCache.get(request.getTid());
                    sessionPoJo.setInsert(false);
                    sessionPoJo.setAvatar(fileUri);
                    chatSessionCache.put(sessionPoJo.getKey(), sessionPoJo);
                    //如果是用户头像或群组头像，还需要同步到云信平台。
                    if (!iYunXinInterface.updateSessionAvatar(sessionPoJo.getId(), sessionPoJo.getOwnerId(), avatar, error)) {
                        System.out.println("---同步群组头像信息到云信平台异常：" + error);
                    }
                }
            }
            return ExecuteResult.success("上传成功", avatar);
        } catch (Exception ex) {
            return ExecuteResult.failure(ex.getMessage(), null);
        }
    }

    public void downloadFile(int type, Long fileId, String suffix, HttpServletResponse response) {
        if (!StringUtils.hasText(suffix)) {
            suffix = "png";
        }

        UploadedFile uploadedFile = uploadedFileRepo.findById(fileId).orElse(null);
        if (uploadedFile != null) {
            suffix = uploadedFile.getSuffixType();
        }

        //文件名称。
        String path = type == 0 ? "user" : (type == 1 ? "group" : "file");
        String fileName = MessageFormat.format("{0}/{1}.{2}", path, "" + fileId, suffix);
        String fileUrl = null;
        try {
            //获取用户头像文件访问路径。
            fileUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(this.bucket)
                    .object(fileName)
                    .build());
        } catch (Exception ex) {
            ex.printStackTrace();
            log.warn(ex.getMessage(), ex);
        }

        boolean isImage = false;
        InputStream inputStream = null;
        try {
            assert fileUrl != null;
            URL url = new URL(fileUrl);
            BufferedImage bufferedImage = ImageIO.read(url);
            if (bufferedImage != null) {
                //是图片文件。
                try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                    ImageIO.write(bufferedImage, suffix.toUpperCase(), outputStream);
                    inputStream = new ByteArrayInputStream(outputStream.toByteArray());
                    isImage = true;
                }
            } else {
                try (InputStream urlOpenStream = url.openStream()) {
                    //不是图片文件，按数据文件处理。
                    inputStream = IOUtils.toBufferedInputStream(urlOpenStream);
                }
            }
        } catch (Exception ex) {
            //ex.printStackTrace();
        }

        //如果文件仍然没有读到，则使用默认文件。
        if (inputStream == null) {
            String defaultAvatar = type == 0 ? "/images/default-user-avatar.png" :
                    (type == 1 ? "/images/default-group-avatar.png" : "/images/not-found.png");
            inputStream = FileService.class.getResourceAsStream(defaultAvatar);
            if (inputStream == null) {
                System.out.println("-----从本地资源中读取默认头像数据----失败-----");
            } else {
                System.out.println("-----从本地资源中读取默认头像数据----成功-----");
            }
            isImage = true;
        }

        if (inputStream != null) {
            try {
                // 清空response
                response.reset();
                // 设置response的Header
                response.setCharacterEncoding("UTF-8");
                //Content-Disposition的作用：告知浏览器以何种方式显示响应返回的文件，用浏览器打开还是以附件的形式下载到本地保存
                //attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
                // filename表示文件的默认名称，因为网络传输只支持URL编码的相关支付，因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
                String filename = URLEncoder.encode(fileId + "." + suffix, StandardCharsets.UTF_8);
                if (isImage) {
                    response.addHeader("Content-Disposition", "inline;filename=" + filename);
                    if (filename.toLowerCase().endsWith(".jpg")) {
                        response.setContentType(MediaType.IMAGE_JPEG_VALUE);
                    } else if (filename.toLowerCase().endsWith(".gif")) {
                        response.setContentType(MediaType.IMAGE_GIF_VALUE);
                    } else {
                        response.setContentType(MediaType.IMAGE_PNG_VALUE);
                    }
                } else {
                    //下载文件方式
                    response.addHeader("Content-Disposition", "attachment;filename=" + filename);
                    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
                }

                // 告知浏览器文件的大小
                int len = 0;
                int total = 0;
                byte[] content = new byte[10 * 1024];
                try (OutputStream output = new BufferedOutputStream(response.getOutputStream())) {
                    do {
                        len = inputStream.read(content);
                        if (len > 0) {
                            output.write(content, 0, len);
                            output.flush();
                            total += len;
                        }
                    } while (len > 0);
                } catch (Exception ignored) {
                }
                response.addHeader("Content-Length", "" + total);
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                try {
                    inputStream.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    private UploadedFile createUploadFileData(Long fileId, int type, String contentType,
                                              String fileName, String suffixType, String url, Long size) {
        return UploadedFile.builder()
                .id(fileId)
                .type(type)
                .contentType(contentType)
                .fileName(fileName)
                .suffixType(suffixType)
                .fileUri(url)
                .fileSize(size)
                .version(0L)
                .createTime(new Timestamp(System.currentTimeMillis()))
                .build();
    }

}
